package centerberg.engine;

import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.Iterator;
import javax.swing.JComponent;
import javax.swing.JFrame;

/**
 * A complete render with a window.
 * 
 * @author Andreas Svanberg
 */
class FrameRender extends Render
{
    private JFrame frame = new JFrame();
    
    private RenderThread renderThread;
    private Scene        nextScene;
    
    private FrameRender()
    {
        ((JComponent)frame.getContentPane()).setOpaque(false);
        frame.setIgnoreRepaint(true);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setResizable(false);
        frame.setSize(800, 600);
        frame.setLocationRelativeTo(null);
    }
    
    /**
     * Returns a scene prepared for rendering by this render.
     * 
     * @return a scene prepared for rendering
     */
    public Scene createScene()
    {
        return new Scene();
    }
    
    /**
     * Returns the instance of this render.
     * 
     * @return the framerender instance
     */
    public static FrameRender getInstance()
    {
        return FrameRenderHolder.INSTANCE;
    }
    
    /**
     * Returns the next scene to render.
     * 
     * @return the next scene to render
     */
    private synchronized Scene getNextScene()
    {
        Scene scene = nextScene;
        nextScene = null;
        return scene;
    }
    
    /**
     * Returns the {@link javax.swing.JFrame} which is being rendered upon.
     * 
     * @return the {@code JFrame}
     */
    public Component getRenderingComponent()
    {
        return frame;
    }
    
    /**
     * Initializes this render.
     * 
     * @return {@code true} if initialization was successfull
     */
    public boolean init()
    {
        frame.setVisible(true);
        Insets is = frame.getInsets();
        frame.setSize(frame.getWidth() + is.left + is.right,
                      frame.getHeight() + is.top + is.bottom);
        frame.createBufferStrategy(2);
        return true;
    }
    
    /**
     * Renders the given scene and starts the rendering thread if neccessary.
     * If there are any scenes currently waiting to be rendered they will be
     * skipped.
     * 
     * @param scene the scene to render
     */
    public synchronized void render(Scene scene)
    {
        nextScene = scene;
        if (renderThread == null || !renderThread.isAlive())
        {
            renderThread = new RenderThread();
            renderThread.start();
        }
    }
    
    /**
     * Sets the size of the render window.
     * 
     * @param width  the width
     * @param height the height
     */
    public void setSize(int width, int height)
    {
        if (frame.isVisible())
        {
            Insets is = frame.getInsets();
            width += is.left + is.right;
            height += is.top + is.bottom;
        }
        frame.setSize(width, height);
    }
    
    private class RenderThread extends Thread
    {
        public void run()
        {
            Insets is = frame.getInsets();
            Scene scene;
            while ((scene = getNextScene()) != null)
            {
                try
                {
                    Graphics2D drawGraphics = (Graphics2D)frame.getBufferStrategy().getDrawGraphics();
                    drawGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    drawGraphics.translate(is.left, is.top);

                    drawGraphics.fillRect(0, 0, frame.getWidth(), frame.getHeight());

                    if (activeCamera != null)
                    {
                        Rectangle viewport = activeCamera.getViewport();
                        drawGraphics.setClip(0, 0, viewport.width, viewport.height);
                        drawGraphics.translate(-viewport.x, -viewport.y);
                    }
                    
                    for (Iterator<Drawable> it = scene.drawObjects(); it.hasNext();)
                    {
                        Drawable drawObject = it.next();
                        
                        drawGraphics.drawImage(
                                drawObject.getTexture().getImage(),
                                drawObject.getTexture().getTransform(),
                                null
                        );
                    }

                    if (!frame.getBufferStrategy().contentsLost())
                        frame.getBufferStrategy().show();
                    drawGraphics.dispose();
                }
                catch (Exception e)
                {
                    // need to send message that render failed (window probably closed)
                    System.exit(0);
                }
            }
        }
    }
    
    private static final class FrameRenderHolder {
        public static final FrameRender INSTANCE = new FrameRender();
    }
}
